using System;
using ClearSpringNet.Hash;
using ClearSpringNet.Util;

namespace ClearSpringNet.Stream.Cardinality
{
    public class AdaptiveCounting : LogLog
    {
        protected int b_e;
        protected double B_s = 0.051;

        public AdaptiveCounting(int k) : base(k)
        {
            b_e = m;
        }

        public AdaptiveCounting(byte[] M) : base(M)
        {
            foreach (byte b in M)
            {
                if (b == 0) 
                    b_e++;
            }
        }

        public bool offer(Object o)
        {
            bool modified = false;
            long x = Lookup3Hash.Lookup3Ycs64((o.ToString()));
            int j = (int)(x >> (Long.Size - k));
            byte r = (byte)(Long.NumberOfLeadingZeros((x << k) | (1 << (k - 1))) + 1);

            if (M[j] < r)
            {
                Rsum += r - M[j];
                if (M[j] == 0) b_e--;
                M[j] = r;
                modified = true;
            }
            return modified;
        }


        public long Cardinality()
        {
            var B = (b_e / (double)m);
            if (B >= B_s)
            {
                return (long) Math.Round(-m * Math.Log(B));
            }
            return base.Cardinality();
        }


        protected static byte rho(long x, int k)
        {
            return (byte)(Long.NumberOfLeadingZeros((x << k) | (1 << (k - 1))) + 1);
        }

        public ICardinality merge(params ICardinality[] estimators)
        {
            return mergeEstimators(PrepMerge(estimators));
        }

        public static AdaptiveCounting mergeEstimators(params LogLog[] estimators)
        {
            AdaptiveCounting merged = null;
            byte[] mergedBytes = MergeBytes(estimators);
            if (mergedBytes != null) merged = new AdaptiveCounting(mergedBytes);
            return merged;
        }

        [Serializable]
        public class Builder : IBuilder<ICardinality>
        {
            private static long serialVersionUID = 2205437102378081334L;

            int k;

            public Builder()
                : this(16)
            {

            }

            public Builder(int k)
            {
                this.k = k;
            }

            public ICardinality build()
            {
                return new AdaptiveCounting(k);
            }

            public int SizeOf()
            {
                return 1 << k;
            }

            /// <summary>
            /// For cardinalities less than 4.25M, obyCount provides a LinearCounting Builder
            /// (see LinearCounting.Builder.onePercentError() ) using only the
            /// space required to provide estimates within 1% of the actual cardinality,
            /// up to ~65k.
            /// 
            /// For cardinalities greater than 4.25M, an AdaptiveCounting builder is returned
            /// that allocates ~65KB and provides estimates with a Gaussian error distribution
            /// with an average error of 0.5% and a standard deviation of 0.5%
            /// 
            /// </summary>
            /// <param name="maxCardinality"></param>
            /// <returns></returns>
            public static IBuilder<ICardinality> obyCount(long maxCardinality)
            {
                if (maxCardinality <= 0) throw new Exception("maxCardinality (" + maxCardinality + ") must be a positive integer");

                if (maxCardinality < 4250000)
                {
                    return LinearCounting.Builder.onePercentError((int)maxCardinality);
                }
                return new Builder(16);
            }
        }
    }
}